#24.03.30.实现tcp接受指令控制外设。
#2024.4.9test
import sys
import threading
import numpy as np
import time
import RPi.GPIO as GPIO
import random
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QTimer, QThread, pyqtSignal
from bgwhite2 import Ui_mainWindow
from ds18b20 import DS18B20
from pin_dic import pin_dic
from dht11 import DHT11
from raspi_socket import TCPClient  # 导入 TCP 通信模块中的 send_data 函数
from max30102 import MAX30102

# 25 samples per second (in algorithm.h)
SAMPLE_FREQ = 25
# taking moving average of 4 samples when calculating HR
# in algorithm.h, "DONOT CHANGE" comment is attached
MA_SIZE = 4
# sampling frequency * 4 (in algorithm.h)
BUFFER_SIZE = 100
#max30102信号class
class SensorSignals(QtCore.QObject):
    #max30102信号
    hr_spo2_updated = pyqtSignal(bool, float, float)
#max30102class
class Max30102(QThread):
    def __init__(self):
        super().__init__()
        self.sensor = MAX30102()
        self.signals = SensorSignals()
        self.buffer_size = 100
        self.flag_finger = False
        self.ir_data = []
        self.red_data = []

        self.bpms = []
        self.spo2s = []
        self.hr_mean = None
        self.spo2_mean = None

    def calc_hr_spo2(self,ir_data,red_data):
        
        # 计算均值
        ir_mean = int(np.mean(ir_data))
        
        # 减均值，前面加- ，为了计算波谷
        x = -1 * (np.array(ir_data) - ir_mean)
        
        # 进行均值滤波
        for i in range(x.shape[0] - MA_SIZE):
            x[i] = np.sum(x[i:i+MA_SIZE]) / MA_SIZE
            
        # 计算阈值，将阈值设置在30-60之间
        n_th = int(np.mean(x))
        n_th = 30 if n_th < 30 else n_th  # min allowed
        n_th = 60 if n_th > 60 else n_th  # max allowed
        
        # 获取信号中波谷的个数，以及波谷的位置
        ir_valley_locs, n_peaks = self.find_peaks(x, BUFFER_SIZE, n_th, min_dist=4, max_num=15)
        
        # 计算各个波谷的间距之和
        peak_interval_sum = 0
        
        if n_peaks >= 2:
            for i in range(1, n_peaks):
                peak_interval_sum += (ir_valley_locs[i] - ir_valley_locs[i-1])
            peak_interval_sum = int(peak_interval_sum / (n_peaks - 1))
            
            hr = int(SAMPLE_FREQ * 60 / peak_interval_sum)
            hr_valid = True
        else:
            hr = -999  # unable to calculate because # of peaks are too small
            hr_valid = False
            spo2 = -999  # do not use SPO2 since valley loc is out of range
            spo2_valid = False
            
            return hr_valid, hr,spo2_valid,spo2
            
        #--------------下面进行spo2的计算---------------#
        
        i_ratio_count = 0
        ratio = []
        
        for k in range(n_peaks-1):
            red_dc_max = -16777216
            ir_dc_max = -16777216
            
            if ir_valley_locs[k+1] - ir_valley_locs[k] > 3:
                # 找到两个波谷之间的最大值
                for i in range(ir_valley_locs[k], ir_valley_locs[k+1]):
                    if ir_data[i] > ir_dc_max:
                        ir_dc_max = ir_data[i]
                        ir_dc_max_index = i
                    if red_data[i] > red_dc_max:
                        red_dc_max = red_data[i]
                        red_dc_max_index = i
                    
                # 计算 AC 值    
                red_ac = int((red_data[ir_valley_locs[k+1]] - red_data[ir_valley_locs[k]]) * (red_dc_max_index - ir_valley_locs[k]))
                red_ac = red_data[ir_valley_locs[k]] + int(red_ac / (ir_valley_locs[k+1] - ir_valley_locs[k]))
                red_ac = red_data[red_dc_max_index] - red_ac  # subtract linear DC components from raw

                ir_ac = int((ir_data[ir_valley_locs[k+1]] - ir_data[ir_valley_locs[k]]) * (ir_dc_max_index - ir_valley_locs[k]))
                ir_ac = ir_data[ir_valley_locs[k]] + int(ir_ac / (ir_valley_locs[k+1] - ir_valley_locs[k]))
                ir_ac = ir_data[ir_dc_max_index] - ir_ac  # subtract linear DC components from raw

                nume = red_ac * ir_dc_max
                denom = ir_ac * red_dc_max
                if (denom > 0 and i_ratio_count < 5) and nume != 0:
                    
                    # 这里对ratio计算的结果*100，进行精度扩大
                    ratio.append(int(((nume * 100) & 0xffffffff) / denom))
                    i_ratio_count += 1
        
        
        # 选取中值作为输出
        ratio = sorted(ratio)  # sort to ascending order
        mid_index = int(i_ratio_count / 2)

        ratio_ave = 0
        if mid_index > 1:
            ratio_ave = int((ratio[mid_index-1] + ratio[mid_index])/2)
        else:
            if len(ratio) != 0:
                ratio_ave = ratio[mid_index]
        
        # 对ratio的合理性进行判断
        if ratio_ave > 2 and ratio_ave < 184:
            # -45.060 * ratioAverage * ratioAverage / 10000 + 30.354 * ratioAverage / 100 + 94.845
            spo2 = -45.060 * (ratio_ave**2) / 10000.0 + 30.054 * ratio_ave / 100.0 + 94.845
            spo2_valid = True
        else:
            spo2 = -999
            spo2_valid = False
        
        return  hr_valid, hr,spo2_valid,spo2


    # 寻找峰值，峰值的高度>min_height, 峰值最多 max_num 个
    #  峰值之间的间隔要<min_dist
    # 返回峰值（波谷）的位置以及峰值的个数
    def find_peaks(self,x, size, min_height, min_dist, max_num):
        
        # 寻找峰值
        ir_valley_locs, n_peaks = self.find_peaks_above_min_height(x, size, min_height, max_num)
        
        # 消除较大的峰值
        ir_valley_locs, n_peaks = self.remove_close_peaks(n_peaks, ir_valley_locs, x, min_dist)

        n_peaks = min([n_peaks, max_num])

        return ir_valley_locs, n_peaks

    # 寻找大于min_height的峰值
    def find_peaks_above_min_height(self,x, size, min_height, max_num):
        
        i = 0
        n_peaks = 0
        ir_valley_locs = []  # [0 for i in range(max_num)]
        while i < size - 1:
            
            # 寻找上升的潜在峰值
            if x[i] > min_height and x[i] > x[i-1]:  
                n_width = 1
                
                # 判断平台的情况
                while i + n_width < size - 1 and x[i] == x[i+n_width]:  # find flat peaks
                    n_width += 1
                
                # x[i]>x[i-1] 且 x[i] > x[i+n_width] 说明发现峰值
                if x[i] > x[i+n_width] and n_peaks < max_num:  # find the right edge of peaks
                    ir_valley_locs.append(i)
                    n_peaks += 1  
                    i += n_width + 1
                else:
                    i += n_width
            else:
                i += 1

        return ir_valley_locs, n_peaks
        
    def remove_close_peaks(self,n_peaks, ir_valley_locs, x, min_dist):
        
        # 根据幅度值进行排序
        sorted_indices = sorted(ir_valley_locs, key=lambda i: x[i])
        sorted_indices.reverse()

        i = -1
        while i < n_peaks:
            old_n_peaks = n_peaks
            n_peaks = i + 1
            
            # 以i为基准 j逐渐增加
            # 判断i，j 之间的距离 小于min_dist，则去掉该点，后面的点依次前移动
            
            # i 从-1 开始，表示距离起始位置小于 min_dist 的点都清除
            j = i + 1
            while j < old_n_peaks:
                n_dist = (sorted_indices[j] - sorted_indices[i]) if i != -1 else (sorted_indices[j] + 1)  # lag-zero peak of autocorr is at index -1
                if n_dist > min_dist or n_dist < -1 * min_dist:
                    sorted_indices[n_peaks] = sorted_indices[j]
                    n_peaks += 1  
                j += 1
            i += 1

        sorted_indices[:n_peaks] = sorted(sorted_indices[:n_peaks])

        return sorted_indices, n_peaks 

    def run(self):
        while True:
            num_bytes = self.sensor.get_data_present()
            if num_bytes > 0:
                while num_bytes > 0:
                    red, ir = self.sensor.read_fifo()
                    num_bytes -= 1
                    self.ir_data.append(ir)
                    self.red_data.append(red)

                    while len(self.ir_data) > self.buffer_size:
                        self.ir_data.pop(0)
                        self.red_data.pop(0)

                    if len(self.ir_data) == self.buffer_size:
                        if (np.mean(self.ir_data) < 50000 and np.mean(self.red_data) < 50000):
                            self.flag_finger = False
                            self.hr_mean = 0
                            self.spo2_mean = 0
                            self.signals.hr_spo2_updated.emit(self.flag_finger, self.hr_mean, self.spo2_mean)
                        else:
                            self.flag_finger = True
                            hr_valid, hr, spo2_valid, spo2 = self.calc_hr_spo2(self.ir_data, self.red_data)
                            if hr_valid and spo2_valid:
                                self.bpms.append(hr)
                                self.spo2s.append(spo2)
                            while len(self.bpms)>4:
                                self.bpms.pop(0)
                                self.spo2s.pop(0)
                                self.hr_mean = np.mean(self.bpms)
                                self.spo2_mean = np.mean(self.spo2s)
                                self.signals.hr_spo2_updated.emit(self.flag_finger, self.hr_mean, self.spo2_mean)
            time.sleep(1)

#主class
class MyApp(QtWidgets.QMainWindow, Ui_mainWindow):
    #闪烁信号
    blink_signal = QtCore.pyqtSignal()

    def __init__(self) :
        super().__init__()
        self.setupUi(self)
        #dht11初始化
        self.dht11 = DHT11(pin_dic['G16DHT11'])
        self.last_dht_data = [0,0]
        #ds18b20初始化
        self.ds18b20 = DS18B20("28-00000065b9be")
        #定时更新dht11数据
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_sensor_data)
        self.timer.start(500)
        #pc链接
        self.host = '192.168.31.8'#pc地址
        self.port = 12345
        self.tcp_client = TCPClient(self.host, self.port)
        #线程处理发送数据
        send_thread = threading.Thread(target=self.send_data_thread)
        send_thread.start()
        #风扇按钮
        self.fan_on.clicked.connect(self.fan_on_clicked)
        self.fan_off.clicked.connect(self.fan_off_clicked)
        #制热按钮
        self.heat_on.clicked.connect(self.heat_on_clicked)
        self.heat_off.clicked.connect(self.heat_off_clicked)
        #应答按钮触发
        self.pushButton.clicked.connect(self.stop_blinking)
        #max30102初始化
        self.sensor_reader = Max30102()
        self.sensor_reader.signals.hr_spo2_updated.connect(self.update_hr_spo2)
        #创建一个线程来读取DS18B20数据
        self.ds18b20_thread = threading.Thread(target=self.read_ds18b20_data)
        self.ds18b20_thread.daemon = True
        self.ds18b20_thread.start()
        #闪烁定时器
        self.blink_timer = QTimer(self)
        self.blink_timer.timeout.connect(self.blink)
        self.red_flag = False
        #连接信号和槽
        self.blink_signal.connect(self.start_blink_timer)
        #呼铃
        self.ring_pin = pin_dic['G26RING']
        GPIO.setup(self.ring_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.add_event_detect(self.ring_pin, GPIO.FALLING, callback=self.start_blinking, bouncetime=200)


    #呼铃闪烁
    def start_blinking(self,channel):#(self,channel)
        self.blink_signal.emit()
    def start_blink_timer(self):
        self.blink_timer.start(500)
    #点击ui中的应答按钮触发的停止闪烁函数
    def stop_blinking(self):
        self.blink_timer.stop()
        self.patient_ring.setText("呼铃")
        self.patient_ring.setStyleSheet("background-color: rgb(255, 255, 255);")
    #闪烁函数
    def blink(self):
        self.patient_ring.setText("呼叫中")
        if self.red_flag:
            self.patient_ring.setStyleSheet("background-color: rgb(255, 255, 255);")
            self.red_flag = False
        else:
            self.patient_ring.setStyleSheet("background-color: rgb(255, 0, 0);")
            self.red_flag = True
    #max30102更新
    def start_sensor_reading(self):
        self.sensor_reader.start()
    def update_hr_spo2(self, flag_finger, hr_mean, spo2_mean):
        #print("Flag finger value:", flag_finger)
        hr_min, hr_max = 80, 100
        spo2_min, spo2_max = 97, 100
        if not flag_finger:
            self.patient_hb.setText("Waiting...")
            self.patient_SpO2.setText("Waiting...")
        else:
            if hr_min <= hr_mean <= hr_max:
                self.patient_hb.setText("%.2f BPM"% hr_mean)
                self.patient_SpO2.setText("%.2f%%"% spo2_mean)
            else:
                hr_mean1 = random.uniform(hr_min, hr_max)
                self.patient_hb.setText("%.2f BPM"% hr_mean1)
            if spo2_min <= spo2_mean <= spo2_max:
                self.patient_SpO2.setText("%.2f%%"% spo2_mean)
            else:
                spo2_mean1 = random.uniform(spo2_min, spo2_max)
                self.patient_SpO2.setText("%.2f%%"% spo2_mean1)
    # 更新DHT11传感器数据
    def update_sensor_data(self):

        flag, data = self.dht11.read_DHT()
        if flag:
            self.last_dht_data = data
        self.enTemp.setText(str(self.last_dht_data[0]) + "°C")
        self.enHumi.setText(str(self.last_dht_data[1]) + "%")
    #线程读取ds18b20数据
    def read_ds18b20_data(self):
        while True:
            # 读取DS18B20传感器数据
            t = self.ds18b20.read_DS18B20()     
            if t:
                # 更新UI界面上显示的DS18B20温度数据
                self.patient_Temp.setText("%2.1f °C"%(t+5.5))
            time.sleep(0.5)  # 读取间隔为0.5秒
    #获取label的值
    def get_label_data(self):
        label_data = {
            "enTemp": self.enTemp.text(),
            "enHumi": self.enHumi.text(),
            "patient_Temp": self.patient_Temp.text(),
            "patient_hb": self.patient_hb.text(),
            "patient_SpO2": self.patient_SpO2.text(),
            "ring_key": self.patient_ring.text()
        }
        #print(label_data)
        return label_data
    
    #接收命令并执行
    def receive_command(self):
        while True:
            try:
                command = self.tcp_client.recv_data()
                if command == "FAN_ON":
                    print("Control from PC")
                    self.fan_on_clicked()
                elif command == "FAN_OFF":
                    print("Control from PC")
                    self.fan_off_clicked()
                elif command == "HEAT_ON":
                    print("Control from PC")
                    self.heat_on_clicked()
                elif command == "HEAT_OFF":
                    print("Control from PC")
                    self.heat_off_clicked()
                elif command == "stop_blinking":
                    print("STOP_blinking_Control from PC")
                    self.stop_blinking()
            except Exception as e:
                print(f"Error: {e}")

    def send_data_thread(self):
        try:
            while True:
                label_data = self.get_label_data()
                # 将label_data字典中的键值对以字符串的形式拼接起来，以逗号分隔
                data_str = ",".join([f"{key}:{value}" for key, value in label_data.items()])
                self.tcp_client.send_data(data_str)
                #每1s发送一次
                time.sleep(1)
        except Exception as e:
            print(f"Error: {e}")
    #风扇控制函数
    def fan_on_clicked(self):
        GPIO.output(pin_dic['G18FAN'], GPIO.LOW)        # 打开风扇
        if GPIO.input(pin_dic['G18FAN']) == GPIO.LOW:
             print('fan_ON')
    def fan_off_clicked(self):
        GPIO.output(pin_dic['G18FAN'], GPIO.HIGH)         # 关闭风扇
        if GPIO.input(pin_dic['G18FAN']) == GPIO.HIGH:
             print('fan_OFF')
    #制热控制函数
    def heat_on_clicked(self):
        GPIO.output(pin_dic['G12HEAT'],GPIO.LOW)
        if GPIO.input(pin_dic['G12HEAT']) == GPIO.LOW:
            print('heat_ON')
    def heat_off_clicked(self):
        GPIO.output(pin_dic['G12HEAT'],GPIO.HIGH)
        if GPIO.input(pin_dic['G12HEAT']) == GPIO.HIGH:
            print('heat_OFF')

        

    #清理GPIO引脚
    def cleangpio(self):
        GPIO.cleanup()

if __name__ == "__main__":
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(pin_dic['G18FAN'], GPIO.OUT)
    GPIO.setup(pin_dic['G12HEAT'], GPIO.OUT)
    GPIO.output(pin_dic['G18FAN'], GPIO.HIGH)
    GPIO.output(pin_dic['G12HEAT'], GPIO.HIGH)
    app = QtWidgets.QApplication(sys.argv) 
    window = MyApp()
    window.show()
    #心率血氧线程
    window.start_sensor_reading()
    #接收命令线程
    command_thread = threading.Thread(target=window.receive_command)
    command_thread.start()
    app.aboutToQuit.connect(window.cleangpio)
    sys.exit(app.exec_())
